package com.zc.utils;

import com.serotonin.modbus4j.BatchRead;
import com.serotonin.modbus4j.BatchResults;
import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.code.DataType;
import com.serotonin.modbus4j.exception.ErrorResponseException;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.serotonin.modbus4j.ip.IpParameters;
import com.serotonin.modbus4j.locator.BaseLocator;
import com.serotonin.modbus4j.msg.ModbusResponse;
import com.serotonin.modbus4j.msg.ReadHoldingRegistersRequest;
import com.serotonin.modbus4j.msg.ReadHoldingRegistersResponse;

/**
 * @description:
 * @author: cong
 * @create: 2025/1/3
 */
public class Modbus4jReadUtils {
    static ModbusFactory modbusFactory;
    static {
        modbusFactory = new ModbusFactory();
    }

    /**
     * 获取Modbus主站实例
     * 该方法用于初始化并返回一个连接到本地Modbus从站的Modbus主站对象
     *
     * @return ModbusMaster 初始化的Modbus主站实例
     * @throws ModbusInitException 如果主站初始化失败，则抛出此异常
     */
    public static ModbusMaster getMaster() throws ModbusInitException {
        // 创建并配置IP参数对象
        IpParameters ipParameters = new IpParameters();
        ipParameters.setHost("192.168.88.33");
        ipParameters.setPort(502);

        // 使用指定的IP参数创建TCP主站实例，并设置保持连接
        ModbusMaster tcpMaster = modbusFactory.createTcpMaster(ipParameters, true);
        // 初始化主站
        tcpMaster.init();
        // 返回初始化的主站实例
        return tcpMaster;
    }

    /**
     * 读取指定从机的线圈状态
     *
     * @param slaveId 从机ID，标识与之通信的设备
     * @param offset 线圈偏移量，指定要读取的线圈位置
     * @return 返回线圈的状态，true表示线圈闭合，false表示线圈断开
     *
     * 此方法使用Modbus协议与从机设备通信，以读取特定线圈的状态
     * 它首先创建一个定位器对象，该对象描述了线圈状态的地址和从机ID，
     * 然后通过Modbus主站发送请求并接收响应，最终返回线圈的状态
     *
     * @throws ModbusInitException 如果Modbus主站初始化失败
     * @throws ModbusTransportException 如果通信传输过程中发生错误
     * @throws ErrorResponseException 如果从机返回错误响应
     */
    public static Boolean readCoilStatus(int slaveId, int offset) throws ModbusInitException, ModbusTransportException, ErrorResponseException {
        // 创建一个定位器对象，用于描述线圈状态的地址和从机ID
        BaseLocator<Boolean> booleanBaseLocator = BaseLocator.coilStatus(slaveId, offset);
        // 通过Modbus主站发送请求并接收响应，返回线圈的状态
        return getMaster().getValue(booleanBaseLocator);
    }

    /**
     * 读取指定从设备的输入状态
     *
     * @param slaveId 从设备的ID，用于标识网络上的不同设备
     * @param offset 输入状态的偏移量，用于指定具体的输入位
     * @return 返回指定从设备的输入状态
     *
     * 此方法使用Modbus协议与从设备通信，通过构建一个定位器来读取特定的输入状态
     * 它可能抛出的异常包括ModbusInitException、ModbusTransportException和ErrorResponseException，
     * 分别表示初始化错误、传输错误和接收到错误响应
     */
    public static Boolean readInputStatus(int slaveId, int offset) throws ModbusInitException, ModbusTransportException, ErrorResponseException {
        // 创建一个定位器，用于读取指定从设备的输入状态
        BaseLocator<Boolean> locator = BaseLocator.inputStatus(slaveId, offset);
        // 通过Modbus主设备读取并返回输入状态
        return getMaster().getValue(locator);
    }

    /**
     * 读取保持寄存器的值
     *
     * @param slaveId 设备ID，标识与之通信的从机设备
     * @param offset 寄存器偏移地址，指定具体寄存器的位置
     * @param dataType 数据类型，定义读取数据的解析方式
     * @return 返回读取到的寄存器值，类型为Number，以便于不同数据类型的处理
     *
     * @throws ModbusInitException 当Modbus通信初始化失败时抛出此异常
     * @throws ModbusTransportException 当Modbus通信传输发生错误时抛出此异常
     * @throws ErrorResponseException 当接收到从机设备的错误响应时抛出此异常
     */
    public static Number readHoldingRegister(int slaveId, int offset, int dataType) throws ModbusInitException, ModbusTransportException, ErrorResponseException {
        // 创建一个定位器，用于指定保持寄存器的位置和数据类型
        BaseLocator<Number> numberBaseLocator = BaseLocator.holdingRegister(slaveId, offset, dataType);
        // 使用定位器从Modbus网络中读取保持寄存器的值
        return getMaster().getValue(numberBaseLocator);
    }

    /**
     * 读取指定从机的保持寄存器数据
     *
     * 此方法通过Modbus协议请求指定从机的保持寄存器数据段，然后解析响应以获取短整型数据数组
     *
     * @param slaveId 从机的地址，用于标识网络上的不同设备
     * @param offset 寄存器的起始地址，表示要读取的数据的开始位置
     * @param count 要读取的寄存器数量，决定了返回数组的长度
     * @return 一个短整型数组，包含了从指定从机的保持寄存器中读取的数据
     * @throws ModbusInitException 如果Modbus通信初始化失败
     * @throws ModbusTransportException 如果在发送请求或接收响应过程中发生传输错误
     * @throws ErrorResponseException 如果从机返回的响应表示有错误
     */
    public static short[] readHoldingRegisters(int slaveId, int offset, int count) throws ModbusInitException, ModbusTransportException, ErrorResponseException {
        // 创建读取保持寄存器的请求
        ReadHoldingRegistersRequest request = new ReadHoldingRegistersRequest(slaveId, offset, count);
        // 发送请求并接收响应
        ReadHoldingRegistersResponse response = (ReadHoldingRegistersResponse) getMaster().send(request);
        // 返回解析后的短整型数据数组
        return response.getShortData();
    }

    /**
     * 读取输入寄存器的值
     *
     * @param slaveId 从机ID，标识与之通信的设备
     * @param offset 寄存器偏移量，指定数据在寄存器中的位置
     * @param dataType 数据类型，定义如何解析寄存器中的数据
     * @return 返回读取到的数值，具体类型为Number的子类
     *
     * @throws ModbusInitException 当Modbus通信初始化失败时抛出此异常
     * @throws ModbusTransportException 当数据传输过程中发生错误时抛出此异常
     * @throws ErrorResponseException 当接收到设备的错误响应时抛出此异常
     */
    public static Number readInputRegisters(int slaveId, int offset, int dataType) throws ModbusInitException, ModbusTransportException, ErrorResponseException {
        // 创建一个定位器，用于指定从哪个从机的哪个寄存器以及以什么数据类型读取数据
        BaseLocator<Number> numberBaseLocator = BaseLocator.inputRegister(slaveId, offset, dataType);
        // 使用定位器从Modbus网络中读取数据，并返回读取到的数值
        return getMaster().getValue(numberBaseLocator);
    }

    /**
     * 批量读取Modbus寄存器的值
     * 该方法用于批量读取指定数量的Modbus寄存器值它创建一个BatchRead对象，为每个寄存器添加一个定位器，
     * 并处理非连续请求最后，它发送批量读取请求并打印结果
     *
     * @param count 要读取的寄存器数量
     * @throws ModbusInitException 如果Modbus初始化失败
     * @throws ModbusTransportException 如果Modbus传输过程中发生错误
     * @throws ErrorResponseException 如果接收到错误响应
     */
    public static void batchRead(Integer count) throws ModbusInitException, ModbusTransportException, ErrorResponseException {
        // 创建一个BatchRead对象用于批量读取
        BatchRead<Integer> batch = new BatchRead<>();
        // 循环为每个寄存器添加定位器
        for (int i = 0; i < count; i++) {
            // 添加定位器，指定寄存器地址和数据类型
            batch.addLocator(i, BaseLocator.holdingRegister(1, i, DataType.TWO_BYTE_INT_SIGNED));
        }
        // 设置不连续请求，以适应非连续的寄存器地址
        batch.setContiguousRequests(false);
        // 发送批量读取请求并获取结果
        BatchResults<Integer> results = getMaster().send(batch);
        // 循环打印读取到的值
        for (int i = 0; i < count; i++) {
            System.out.println(results.getValue(i));
        }
    }
}
